#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <dirent.h> // 目录操作
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h> // 定义了全局错误码 errno
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

#define SIZE_1M 1024 * 1024 // 1M以下的文件，直接一次性拷贝
#define SIZE_10M 10 * 1024 * 1024
#define SIZE_100M 100 * 1024 * 1024 // 100M以下的文件，buf = 1M

// 返回成功拷贝的字节数
int copyfile(int fd1, int fd2)
{
  // 循环读取源文件，写入目标文件
  // 根据文件实际大小分配缓冲区
  long file_size = lseek(fd1, 0, SEEK_END);
  lseek(fd1, 0, SEEK_SET);

  int heap_size = 0; //
  if (file_size < SIZE_1M)
    heap_size = file_size; // 1M以下的文件，直接一次性拷贝
  else if (file_size < SIZE_100M)
    heap_size = SIZE_1M;
  else
    heap_size = SIZE_10M; // 100M以上的文件，buf = 10M

  char *buf = malloc(heap_size);
  int total = 0;
  while (1)
  {
    bzero(buf, heap_size);
    int n = read(fd1, buf, heap_size);

    if (n < 0)
    {
      // read的过程中被信号中断导致出错，则再读一次即可
      if (errno == EINTR)
        continue;
      printf("读取失败");
      return -1;
    }

    if (n == 0)
      return total;

    if (n > 0)
    {
      int t = 0;
      while (n > 0)
      {
        int m = write(fd2, buf + t, n); // m <= n
        if (m <= 0)
        {
          perror("写入失败");
          return -1;
        }

        t += m;
        n -= m;
      }
    }
  }

  close(fd1);
  close(fd2);
}

void copydir(const char *dir1, const char *dir2)
{
  // 取得绝对路径，便于后续跳转
  char ori_path[50] = {0}; // 程序启动时的绝对路径
  char src_path[50] = {0}; // 源文件的绝对路径
  char dst_path[50] = {0}; // 目标文件的绝对路径

  getcwd(ori_path, 50); // /mnt/hgfs/code/0812

  chdir(dir1);
  getcwd(src_path, 50); // /mnt/hgfs/code/xx

  chdir(ori_path);
  mkdir(dir2, 0755);
  chdir(dir2);
  getcwd(dst_path, 50); // /home/gec/yy

  printf("ori_path: %s\n", ori_path);
  printf("src_path: %s\n", src_path);
  printf("dst_path: %s\n", dst_path);

  DIR *dp = opendir(src_path);
  if (dp == NULL)
  {
    printf("打开%s失败: %s", src_path, strerror(errno));
    return;
  }

  struct dirent *ep;
  struct stat st;
  while (1)
  {
    ep = readdir(dp);
    if (ep == NULL)
      break;

    // 跳过.和..
    if (strcmp(ep->d_name, ".") == 0 ||
        strcmp(ep->d_name, "..") == 0)
      continue;

    // 切换到源目录下
    chdir(src_path);

    // 判断即将要的拷贝文件的类型
    bzero(&st, sizeof(st));
    stat(ep->d_name, &st);

    if (S_ISREG(st.st_mode))
    {
      int fd1 = open(ep->d_name, O_RDONLY);

      chdir(dst_path);
      int fd2 = open(ep->d_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);

      copyfile(fd1, fd2);
    }

    else if (S_ISDIR(st.st_mode))
    {
      // 创建目标目录，并获得其绝对路径
      chdir(dst_path);
      mkdir(ep->d_name, 0755);
      chdir(ep->d_name);
      char path[50] = {0};
      getcwd(path, 50);

      chdir(src_path);
      copydir(ep->d_name, path);
    }

    else
    {
      printf("不支持的文件: %s/%s，略过\n", src_path, ep->d_name);
    }
  }
}

//  程序位置 :  /mnt/hgfs/code/0812
int main(int argc, char const *argv[]) // ./mycp  ../xx/   /home/gec/yy/
{
  // 0. 检查参数
  if (argc != 3)
  {
    printf("请指定源文件和目标文件\n");
    exit(0);
  }

  // 1. 判定要拷贝的文件类型
  struct stat st;
  bzero(&st, sizeof(st));
  stat(argv[1], &st);
  if (S_ISREG(st.st_mode))
  {
    // 分别打开两个文件
    int fd1 = open(argv[1], O_RDONLY);
    int fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);

    copyfile(fd1, fd2);
  }
  else if (S_ISDIR(st.st_mode))
  {
    copydir(argv[1], argv[2]);
  }
  else
    printf("指定文件无法拷贝\n");

  return 0;
}
